﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Dxr.Data.Mapping;
using Zongsoft.Data;

namespace Dxr.Data
{
    public static class ObjectAccessExtension
    {
        internal static string ToWhere(this ICondition condition, Dictionary<string, ColumnInfo> columnInfos, ref int tableIndex, ref int joinStartIndex, ref int valueIndex, out object[] values, string format = "WHERE {0}")
        {
            var where = Resolve(condition, columnInfos, ref tableIndex, ref joinStartIndex, ref valueIndex, out values);
            if (string.IsNullOrEmpty(where))
                return "";
            return string.Format(format, where);
        }

        private static string Resolve(ICondition condition, Dictionary<string, ColumnInfo> columnInfos, ref int tableIndex, ref int joinStartIndex, ref int valueIndex, out object[] values)
        {
            if (condition is IConditional)
                condition = ((IConditional)condition).ToConditions();

            if (condition is Condition)
            {
                var where = (Condition)condition;
                values = GetValue(where);

                if (!columnInfos.ContainsKey(where.Name))
                    throw new Exception(string.Format("未找到属性\"{0}\"的描述信息", where.Name));

                var columnInfo = columnInfos[where.Name];

                var oper = where.Operator.Parse(ref values, ref tableIndex, ref joinStartIndex, ref valueIndex);

                if (string.IsNullOrEmpty(oper))
                    return string.Format("{0} != {0}", columnInfo.ToColumn());
                else
                    return string.Format("{0} {1}", columnInfo.ToColumn(), oper);
            }
            else if (condition is ConditionCollection)
            {
                var where = (ConditionCollection)condition;
                var sqls = new List<string>();
                var vs = new List<object>();

                foreach (var item in where)
                {
                    var result = Resolve(item, columnInfos, ref tableIndex, ref joinStartIndex, ref valueIndex, out values);
                    if (string.IsNullOrEmpty(result))
                        continue;

                    if (item is ConditionCollection)
                        result = string.Format("({0})", result);

                    sqls.Add(result);
                    vs.AddRange(values);
                }
                values = vs.ToArray();

                if (sqls.Count == 0)
                    return null;
                if (sqls.Count == 1)
                    return sqls[0];

                return string.Join(where.ConditionCombination.Parse(), sqls);
            }

            values = new object[0];
            return null;
        }

        private static string Parse(this ConditionOperator clauseOperator, ref object[] values, ref int tableIndex, ref int joinStartIndex, ref int valueIndex)
        {
            switch (clauseOperator)
            {
                case ConditionOperator.Between:
                    return string.Format("BETWEEN {{{0}}} AND {{{1}}}", valueIndex++, valueIndex++);
                case ConditionOperator.Equal:
                case ConditionOperator.Like:
                    {
                        if (values == null || values.Length == 0)
                            return "IS NULL";
                        return string.Format("{0} {{{1}}}", values[0] is string && ((string)values[0]).IndexOfAny("_%".ToArray()) >= 0 ? "LIKE" : "=", valueIndex++);
                    }
                case ConditionOperator.GreaterThan:
                    return string.Format("> {{{0}}}", valueIndex++);
                case ConditionOperator.GreaterThanEqual:
                    return string.Format(">= {{{0}}}", valueIndex++);
                case ConditionOperator.In:
                case ConditionOperator.NotIn:
                    {
                        if (values == null || values.Length == 0)
                            return "";

                        if (values.Length == 1)
                        {
                            var value = values[0];
                            if (value is ObjectAccessResult)
                            {
                                var objectAccessResult = (ObjectAccessResult)value;
                                var parameter = new CreatingSqlParameter(true, tableIndex, joinStartIndex, valueIndex);
                                parameter.ConditionOperator = clauseOperator;
                                var result = objectAccessResult.CreateSql(parameter);

                                tableIndex = parameter.TableIndex;
                                joinStartIndex = parameter.JoinStartIndex;
                                valueIndex = parameter.ValueIndex;
                                values = result.Values;

                                return string.Format("{0} ({1})", clauseOperator == ConditionOperator.NotIn ? "NOT IN" : "IN", result.Sql);
                            }
                            return string.Format("{0} {{{1}}}", clauseOperator == ConditionOperator.NotIn ? "!=" : "=", valueIndex++);
                        }

                        var list = new List<string>();
                        for (int i = 0; i < values.Length; i++)
                        {
                            list.Add(string.Format("{{{0}}}", valueIndex + i));
                        }

                        valueIndex += values.Length;
                        return string.Format("{0} ({1})", clauseOperator == ConditionOperator.NotIn ? "NOT IN" : "IN", string.Join(",", list));
                    }

                case ConditionOperator.LessThan:
                    return string.Format("< {{{0}}}", valueIndex++);
                case ConditionOperator.LessThanEqual:
                    return string.Format("<= {{{0}}}", valueIndex++);
                case ConditionOperator.NotEqual:
                    {
                        if (values == null || values.Length == 0)
                            return "IS NOT NULL";

                        return string.Format("!= {{{0}}}", valueIndex++);
                    }
                default:
                    throw new ArgumentOutOfRangeException("未知的clauseOperator");
            }
        }

        private static string Parse(this ConditionCombination combine)
        {
            switch (combine)
            {
                case ConditionCombination.Or:
                    return " OR ";
                default:
                    return " AND ";
            }
        }

        private static string Parse(this SortingMode sortingMode)
        {
            switch (sortingMode)
            {
                case SortingMode.Ascending:
                    return "ASC";
                case SortingMode.Descending:
                    return "DESC";
                default:
                    throw new ArgumentOutOfRangeException(string.Format("未存在当前枚举值:{0}", sortingMode));
            }
        }

        public static string Parse(this Sorting sorting, ClassNode info, string tableEx)
        {
            if (!string.IsNullOrEmpty(tableEx))
                tableEx += ".";

            var sort = sorting.Mode.Parse();
            return string.Join(",", sorting.Members.Select(p =>
            {
                var pi = info.PropertyNodeList.FirstOrDefault(i => i.Name == p);
                return string.Format("{0}\"{1}\" {2}", tableEx, pi == null ? p : pi.Field, sort);
            }));
        }

        internal static string Parse(this Sorting sorting, Dictionary<string, ColumnInfo> columnInfos)
        {
            var sort = sorting.Mode.Parse();
            var format = "{0} {1}";

            return string.Join(",", sorting.Members.Select(p =>
            {

                if (!columnInfos.ContainsKey(p))
                    throw new Exception(string.Format("未找到属性\"{0}\"的描述信息", p));

                var columnInfo = columnInfos[p];

                return string.Format(format, columnInfo.ToColumn(), sort);
            }));
        }

        private static object[] GetValue(Condition where)
        {
            if (where.Value == null)
                return new object[0];
            else if (where.Value is string || where.Value is ObjectAccessResult)
                return new[] { where.Value };
            else if (where.Value is object[])
                return (object[])where.Value;
            else if (where.Value is IEnumerable<object>)
                return ((IEnumerable<object>)where.Value).ToArray();
            else if (where.Value is IEnumerable)
                return ((IEnumerable)where.Value).Cast<object>().ToArray();
            else
                return new[] { where.Value };
        }
    }
}
